/*
  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  PARTICULAR PURPOSE.
  
    This is sample code and is freely distributable.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using svs.Mobile.BeyondMedia.Controls;
using svs.Mobile.BeyondMedia.GraphicsAPI;
using svs.Mobile.BeyondMedia.Objects;

namespace svs.Mobile.BeyondMedia.Controls
{
	internal class OwnerDrawnList<T> : Control
	{
		#region Data Members

		private readonly int m_scrollWidth;
		private int m_itemHeight = -1;
		private int m_selectedIndex = -1;
		private int m_touchedIndex = -1;
		private readonly string m_OnEmptyText = "There are no items in this view";
		private Bitmap m_offScreen;
		private readonly VScrollBar m_vScrollBar;
		private readonly List<T> m_Items;
		protected bool EnableKeyNavigation = true;
		protected bool EnableMouseNavigation = true;
		public event EventHandler SelectedIndexChanged;
		public event EventHandler BackClick;
		protected DragHelper m_Helper = new DragHelper();
		protected readonly Color ItemTouchedColor = Color.Gainsboro;
		protected Color ItemSeparatorColor = Color.Gray;
		protected readonly Color ItemHighLightColor1 = SystemColors.Highlight;
		protected readonly Color ItemHighLightColor2 = SystemColors.InactiveCaption;
		

		#endregion

		#region Constructors

		public OwnerDrawnList()
		{
			m_vScrollBar = new VScrollBar();
			m_vScrollBar.Width = Helpers.SystemScrollbarWidth;
			m_scrollWidth = m_vScrollBar.Width;
			m_vScrollBar.Parent = this;
			m_vScrollBar.Visible = false;
			m_vScrollBar.SmallChange = 1;
			m_vScrollBar.ValueChanged += new EventHandler(ScrollValueChanged);

			// Items to draw
			m_Items = new List<T>();

			BackColor = Color.FromArgb(224, 236, 253);

			MouseDown += m_Helper.OnMouseDown;
			MouseUp += m_Helper.OnMouseUp;
			MouseMove += m_Helper.OnMouseMove;

			m_Helper.Click += OnMouseClicked;
			m_Helper.Drag += OnDrag;
			m_Helper.Touch += OnTouch;


		}

		#endregion

		#region Protected Properties

		public int TouchedIndex
		{
			get
			{
				return m_touchedIndex;
			}

			protected	set
			{
				m_touchedIndex = value;
			}
		}

		protected Bitmap OffScreen
		{
			get { return m_offScreen; }
		}

		protected VScrollBar VScrollBar
		{
			get { return m_vScrollBar; }
		}

		protected virtual int ItemHeight
		{
			get { return m_itemHeight; }
			set { m_itemHeight = value; }
		}

		protected int DrawCount
		{
			get
			{
				if (m_vScrollBar.Value + m_vScrollBar.LargeChange > m_vScrollBar.Maximum)
					return m_vScrollBar.Maximum - m_vScrollBar.Value + 1;
				else
					return m_vScrollBar.LargeChange;
			}
		}

		#endregion

		#region Public Properties
		
		[Browsable(false)]
		public virtual string OnEmptyText
		{
			get { return m_OnEmptyText; }
		}

		[Browsable(false)]
		public int SelectedIndex
		{
			get { return m_selectedIndex; }

			protected set
			{
				m_selectedIndex = value;
				if (SelectedIndexChanged != null)
					SelectedIndexChanged(this, EventArgs.Empty);
			}
		}

		public List<T> Items
		{
			get { return m_Items; }
		}

		#endregion

		#region Public Methods

		public void EnsureVisible(int index)
		{
			if (index >= 0)
			{
				if (index < m_vScrollBar.Value)
				{
					m_vScrollBar.Value = index;
				}
				else if (index >= m_vScrollBar.Value + DrawCount)
				{
					m_vScrollBar.Value = index - DrawCount + 1;
				}
			}

			Refresh();

			if (Visible)
				Focus();
		}

		#endregion

		#region Protected Methods

		protected override void OnPaint(PaintEventArgs e)
		{
			OnPaintEmptyList(e);
		}

		protected void OnPaintEmptyList(PaintEventArgs e)
		{
			if (Items.Count == 0)
			{
				using (Font font = new Font(Font.Name, 10, FontStyle.Italic))
				{
					Rectangle rect = ClientRectangle;
					rect.Inflate(-10, -10);

					using (Brush b = new SolidBrush(Color.Gray))
					{
						e.Graphics.DrawString(OnEmptyText, font, b, rect);
					}
				}
			}
		}

		protected virtual Graphics PrepareOffscreenGraphics()
		{
			Graphics gOffScreen = Graphics.FromImage(OffScreen);
			GradientFill.Fill(gOffScreen, ClientRectangle, Color.White, Color.Gainsboro, GradientFill.FillDirection.LeftToRight);
			return gOffScreen;
		}

		protected void OnTouch(MouseEventArgs e)
		{
			TouchedIndex = GetItemIndexAtCoordinates(e.X, e.Y);
			Refresh();
		}

		protected virtual void OnMouseClicked(MouseEventArgs e, DragHelper.ClickSpeed speed)
		{
			if (!EnableMouseNavigation)
				return;

			TouchedIndex = -1;

			if(e == null) // click based on the slow click timer expiring
			{
				Point client = PointToClient(new Point(Control.MousePosition.X, Control.MousePosition.Y));
				e = new MouseEventArgs(MouseButtons, 0, client.X, client.Y, 0);
			}

			int index = GetItemIndexAtCoordinates(e.X, e.Y);
			if(index != -1 )
			{
				ItemClicked(index, e.X, e.Y, speed);
			}
		}

		private void OnDrag(DragHelper.DragDirection direction, DragHelper.DragSpeed speed)
		{
			int increment = 0;

			TouchedIndex = -1;

			switch (speed)
			{
				case DragHelper.DragSpeed.Fast:
					increment = VScrollBar.SmallChange * 5;
					break;

				case DragHelper.DragSpeed.Medium:
					increment = VScrollBar.SmallChange * 3;
					break;

				case DragHelper.DragSpeed.Slow:
					increment = VScrollBar.SmallChange;
					break;
			}

			if (direction == DragHelper.DragDirection.N || direction == DragHelper.DragDirection.NE || direction == DragHelper.DragDirection.NW)
				VScrollBar.Value += increment;
			else
				VScrollBar.Value -= increment;
		}

		private int  GetItemIndexAtCoordinates(int X, int Y)
		{
			int itemTop = 0;

			for (int n = VScrollBar.Value; n <= VScrollBar.Value + DrawCount; n++)
			{
				Rectangle itemArea;
				itemArea = new Rectangle(0,
				                         itemTop,
				                         ClientSize.Width - (VScrollBar.Visible ? VScrollBar.Width : 0),
				                         ItemHeight);
				if (n > Items.Count - 1)
					break;

				if (itemArea.Contains(X, Y))
				{
					return n;
				}

				// Set the next item top to move down the item height
				itemTop += ItemHeight;
			}

			return -1;
		}

		protected virtual void ItemClicked(int i_ItemIndex, int X, int Y, DragHelper.ClickSpeed speed)
		{
			SelectedIndex = i_ItemIndex;
			Refresh();
		}

		protected virtual void OnSelectedIndexChanged(EventArgs e)
		{
			if (SelectedIndexChanged != null)
				SelectedIndexChanged(this, e);
		}

		protected void ScrollValueChanged(object o, EventArgs e)
		{
			Refresh();
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			if (EnableKeyNavigation)
			{
				switch (e.KeyCode)
				{
					case Keys.Down:
						if (SelectedIndex < m_vScrollBar.Maximum)
						{
							++SelectedIndex;
						}
						else
						{
							SelectedIndex = m_vScrollBar.Minimum;
						}

						EnsureVisible(SelectedIndex);

						e.Handled = true;
						break;

					case Keys.Up:
						if (SelectedIndex > m_vScrollBar.Minimum)
						{
							--SelectedIndex;
						}
						else
						{
							SelectedIndex = m_vScrollBar.Maximum;
						}

						EnsureVisible(SelectedIndex);

						e.Handled = true;
						break;
				}

				//Patch the * and # symbols
				if((int)e.KeyData == 120)
					FindNextItemStartingWith('A');
				else if((int)e.KeyData == 119)
					FindNextItemStartingWith('Z');
			}

			base.OnKeyDown(e);
		}

		protected override void OnKeyPress(KeyPressEventArgs e)
		{
			if (e.KeyChar == (Char) Keys.Escape)
			{
				e.Handled = true;
				if (BackClick != null)
					BackClick(this, null);
			}

			FindNextItemStartingWith(e.KeyChar);
		}

		protected override void OnResize(EventArgs e)
		{
			int viewableItemCount = ClientSize.Height/ItemHeight;

			m_vScrollBar.Bounds = new Rectangle(ClientSize.Width - m_scrollWidth,
			                                    0,
			                                    m_scrollWidth,
			                                    ClientSize.Height);

			try
			{
				// Determine if scrollbars are needed
				if (m_Items.Count > viewableItemCount)
				{
					m_vScrollBar.Visible = true;
					m_vScrollBar.LargeChange = viewableItemCount;
					m_offScreen = new Bitmap(ClientSize.Width - m_scrollWidth, ClientSize.Height);
				}
				else
				{
					m_vScrollBar.Visible = false;
					m_vScrollBar.LargeChange = m_Items.Count;
					m_offScreen = new Bitmap(ClientSize.Width, ClientSize.Height);
				}
			}
			catch(OutOfMemoryException ex)
			{
				Helpers.NotifyOutOfMemory(ex);
			}

			m_vScrollBar.Maximum = m_Items.Count - 1;
		}

		// Determine what the text color should be
		// for the selected item drawn as highlighted
		protected Color CalcTextColor(Color backgroundColor)
		{
			if (backgroundColor.Equals(Color.Empty))
				return Color.Black;

			int sum = backgroundColor.R + backgroundColor.G + backgroundColor.B;

			if (sum > 256)
				return Color.Black;
			else
				return Color.White;
		}

		protected bool FindNextItemStartingWith(char keyChar)
		{
			if (Items.Count == 0)
				return false;

			int start, curr;

			if (SelectedIndex > 0 && m_selectedIndex < Items.Count)
				curr = SelectedIndex;
			else
				curr = 0;

			start = curr;

			do
			{
				curr++;

				if (curr > Items.Count - 1)
					curr = 0;

				if (GetItemText(curr).StartsWith(keyChar.ToString(), StringComparison.InvariantCultureIgnoreCase)
				    ||
				    GetItemText(curr).StartsWith(Helpers.GetAlternativeKey(keyChar).ToString(),
				                                 StringComparison.InvariantCultureIgnoreCase))
				{
					SelectedIndex = curr;
					EnsureVisible(SelectedIndex);
					return true;
				}
			} while (curr != start);

			return false;
		}

		protected virtual string GetItemText(int ItemIndex)
		{
			return string.Empty;
		}

		#endregion
	}
}